# Copyright (c) 2021 by xfangfang. All Rights Reserved.
#
#

import os
import sys
import json
import time
import socket
import random
import subprocess
import logging
import threading
import cherrypy
import gettext
from enum import Enum

from macast.utils import Setting
from macast.renderer import Renderer

if os.name == 'nt':
    import _winapi
    from multiprocessing.connection import PipeConnection

logger = logging.getLogger("UDPRenderer")
logger.setLevel(logging.INFO)

class UDPRenderer(Renderer):
    """
      When the DLNA client accesses, UDPRenderer will returns the state value
    corresponding to "out" section in the action specified in the service XML
    file by default.
      When some "service_action" methods are implemented
    (such as "RenderingControl_SetVolume"), the DLNA client's access will be
    automatically directed to these methods
    """

    def __init__(self, lang=gettext.gettext):
        super(UDPRenderer, self).__init__(lang)
        global _
        _ = lang
        self.title = Setting.get_friendly_name()
        self.pause = False  # changed with pause action
        self.playing = False  # changed with start and stop
        self.udp_thread = None
        self.udp_sock = None
        self.udp_running = False
        self.command_lock = threading.Lock()
        self.receive_addr = ('0.0.0.0', 8882)
        #self.send_addr = ('192.168.1.3', 8881)
        self.send_addr = ('0.0.0.0', 8881)
    def set_media_stop(self):
        self.send_command('{"Action": "Stop"}')

    def set_media_pause(self):
        self.send_command('{"Action": "Pause"}')

    def set_media_resume(self):
        self.send_command('{"Action": "Resume"}')

    def set_media_volume(self, data):
        """ data : int, range from 0 to 100
        """
        self.send_command('{"Action": "SetVolume", "Content": '+str(data)+'}')

    def set_media_mute(self, data):
        """ data : bool
        """
        if data:
            self.send_command('{"Action": "Mute", "Content": true}')
        else:
            self.send_command('{"Action": "Mute", "Content": false}')

    def set_media_url(self, url, start="0"):
        """ data : string
        """
        self.send_command('{"Action": "Player", "Content": "'+url+'"}')

    def set_media_title(self, data):
        """ data : string
        """
        self.title = data
        self.send_command('{"Action": "SetTitle", "Content": "'+data+'"}')

    def set_media_position(self, data):
        """ data : position, 00:00:00
        """
        self.send_command('{"Action": "Seek", "Content": "'+data+'"}')

    def set_media_sub_file(self, data):
        pass

    def set_media_sub_show(self, data: bool):
        pass

    def set_media_text(self, data: str, duration: int = 1000):
        pass

    def set_media_speed(self, data: float = 1):
        """ data: range(0.01 - 100)
        """
        self.send_command('{"Action": "Speed", "Content": '+str(data)+'}')
        pass

    def update_state(self, res):
        """Update player state from udpPlyer
        """
        res = json.loads(res)

        if 'FriendlyName' in res and res['FriendlyName'] is not None:
            Setting.set_temp_friendly_name(str(res['FriendlyName']))
            self.protocol.handler.build_description()

        if 'DurationTime' not in res or res['DurationTime'] is None:
            duration = '00:00:00'
        else:
            sec = int(res['DurationTime'])
            duration = '%d:%02d:%02d' % (sec // 3600, (sec % 3600) // 60, sec % 60)
            cherrypy.engine.publish('udp_update_duration', duration)
            logger.info("update duration " + duration)
            if self.protocol.get_state_transport_state() == 'PLAYING':
                logger.debug("Living media")
        self.set_state_duration(duration)

        if 'PositionTime' not in res or res['PositionTime'] is None:
            position = '00:00:00'
        else:
            sec = int(res['PositionTime'])
            position = '%d:%02d:%02d' % (sec // 3600, (sec % 3600) // 60, sec % 60)
        self.set_state_position(position)

        if 'Volume' in res and res['Volume'] is not None:
            self.set_state_volume(int(res['Volume']))

        if 'Playing' in res and res['Playing'] is not None:
            if self.playing is False:
                return
            if res['Playing']:
                self.pause = False
                self.playing = True
                self.set_state_play()
            else:
                self.pause = True
                self.set_state_pause()

    def send_command(self, command):
        """Sending command to udp
        """
        logger.debug("send command: " + command)
       
        with self.command_lock:
            try:
                sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                sock.sendto(command.encode(), self.send_addr)
                sock.close()
                return True
            except Exception as e:
                logger.error('sendCommand: ' + str(e))
                return False

    def start_udp(self):
        """Start udp thread
        """
        if self.udp_running:
            logger.error("udp receive thread is already runing")
            return
        self.udp_running = True

        while self.udp_running:
            try:
                time.sleep(0.5)
                logger.info("udp receive thread start bind")
                self.udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                self.udp_sock.bind(self.receive_addr)

                cherrypy.engine.publish('udp_start')
                cherrypy.engine.publish('renderer_start')
            except Exception as e:
                logger.error("udp receive thread reconnecting: {}".format(str(e)))
                continue
            
            while self.udp_running:
                try:
                    data, addr = self.udp_sock.recvfrom(512)
                    print("Received udp message from ", addr)
                    if data == b'':
                        break
                    print("Received udp message ", data.decode())
                    logger.debug("Received udp message: {}".format(data.decode()))
                    self.update_state(data.decode())
                except Exception as e:
                    logger.debug(e)
                    break
            self.udp_sock.close()
            logger.error("udp receive thread stopped")

    def start(self):
        """Start
        """
        super(UDPRenderer, self).start()
        logger.info("starting udp thread")
        self.udp_thread = threading.Thread(target=self.start_udp, name="UDP_THREAD")
        self.udp_thread.start()

    def stop(self):
        """Stop
        """
        super(UDPRenderer, self).stop()
        logger.info("stoping udp thread")
        
        try:
            os.waitpid(-1, 1)
        except Exception as e:
            logger.error(e)
        # stop thread
        self.udp_running = False
        self.udp_thread.join()

    def reload(self):
        """Reload 
        """
        uri = self.protocol.get_state_url()

        def loadfile():
            logger.debug("reload loadfile")
            cherrypy.engine.unsubscribe('udp_start', loadfile)

        def restart():
            self.stop()
            self.start()

        if self.protocol.get_state_transport_state() == 'PLAYING':
            cherrypy.engine.subscribe('udp_start', loadfile)

        threading.Thread(target=restart, args=()).start()